package org.fhm.zdte.common.service.servlet;

import org.fhm.substrate.annotation.Setup;
import org.fhm.substrate.service.LoggerHandler;
import org.fhm.substrate.standard.ILogger;
import org.fhm.zdte.common.annotation.RequestMapping;
import org.fhm.zdte.common.exception.ServletException;
import org.fhm.zdte.common.pojo.Result;
import org.fhm.zdte.common.pojo.Servlet;
import org.fhm.zdte.common.standard.IInterfaceScheduler;
import org.fhm.zdte.common.standard.IParametersAnnotationMatch;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

/**
 * The abstract layer for interface scheduler.
 *
 * @author 谭波
 * @since 2023/12/28
 */
@SuppressWarnings("unused")
public abstract class AbstractInterfaceScheduler implements IInterfaceScheduler {

    private final ILogger logger = LoggerHandler.getLogger(AbstractInterfaceScheduler.class);

    /**
     * The container for store {@link Servlet}.
     */
    private final Map<String, Servlet> servletContainer = new HashMap<>();

    @Setup
    private Map<String, IParametersAnnotationMatch> parametersAnnotationMatchMap;

    @Override
    public void mappingInterfaceWithMethods(List<?> beans) throws ServletException {
        for (Object bean : beans) {
            Class<?> beanClazz = bean.getClass();
            RequestMapping headMapper = beanClazz.getAnnotation(RequestMapping.class);
            String headUrl = "";
            if (Objects.nonNull(headMapper))
                headUrl = headMapper.value();
            for (Method method : beanClazz.getDeclaredMethods()) {
                if (method.isAnnotationPresent(RequestMapping.class)) {
                    method.setAccessible(true);
                    servletContainer.put(
                            checkoutMethodAndGetUrl(method, headUrl),
                            new Servlet(bean, method)
                    );
                }
            }
        }
    }

    private String checkoutMethodAndGetUrl(Method method, String headUrl) throws ServletException {
        String interfaceUrl = headUrl + method.getAnnotation(RequestMapping.class).value();
        if (!method.getReturnType().isAssignableFrom(Result.class))
            throw new ServletException("the method returns of the interface url " + interfaceUrl + " must be Result");
        for (Parameter parameter : method.getParameters()) {
            Class<?> parameterType = parameter.getType();
            if (parameterType.isInterface() || parameterType.isAnnotation() || parameterType.isEnum())
                throw new ServletException("the parameter type is not allowed to be an interface、 annotation or enum");
            Annotation[] annotations = parameter.getAnnotations();
            if (annotations.length == 0)
                throw new ServletException("the interface " + interfaceUrl +
                        " has no specify parameter information by the annotation for parameters");
            if (annotations.length > 1)
                throw new ServletException("the interface " + interfaceUrl +
                        " repeat specify parameter information by the annotation for parameters");
            String annotationName;
            IParametersAnnotationMatch annotationMatch;
            if (
                    Objects.isNull(
                            (annotationMatch = parametersAnnotationMatchMap
                                    .get((annotationName = annotations[0].annotationType().getSimpleName())))
                    )
            )
                throw new ServletException("The annotation " + annotationName + " of the method of interface " + interfaceUrl +
                        " does not implement a parameter matching mechanism");
            annotationMatch.isValidForParameter(parameter);
        }
        if (servletContainer.containsKey(interfaceUrl))
            throw new ServletException("The interface url" + interfaceUrl + " of servlet is duplicated");
        return interfaceUrl;
    }

    @Override
    public Result<?> invokedMethodByInterfaceUrl(String from, String interfaceUrl, Map<String, Object> params) {
        if (interfaceUrl.isEmpty()) {
            logger.warn("the API {} is illegal", interfaceUrl);
            return Result.getFailMessage("the API " + interfaceUrl + " is illegal");
        }
        Servlet servlet = servletContainer.get(interfaceUrl);
        if (Objects.isNull(servlet)) {
            logger.warn("the API {} no match", interfaceUrl);
            return Result.getFailMessage("the API " + interfaceUrl + " no match");
        }
        Method method = servlet.getMethod();
        List<Object> parameters = new ArrayList<>();
        try {
            warpMethodParameters(params, method, interfaceUrl, parameters);
        } catch (Exception e) {
            return Result.getFailResult(e.getMessage());
        }
        Result<?> result;
        try {
            result = (Result<?>) method.invoke(servlet.getBean(), parameters.toArray());
            if (!from.isEmpty())
                logger.info("the client that identification is {} calls the interface url {}", from, interfaceUrl);
            return result;
        } catch (IllegalAccessException | InvocationTargetException e) {
            return Result.getFailMessage(e.getMessage());
        }
    }

    private void warpMethodParameters(
            Map<String, Object> params,
            Method method,
            String interfaceUrl,
            List<Object> parameters
    ) throws Exception {
        for (Parameter parameter : method.getParameters()) {
            IParametersAnnotationMatch annotationMatch = parametersAnnotationMatchMap
                    .get(parameter.getAnnotations()[0].annotationType().getSimpleName());
            if (Objects.isNull(annotationMatch))
                throw new ServletException("the interface " + interfaceUrl +
                        " has no specify parameter information by the prescribed annotation for parameters");
            else
                annotationMatch.match(params, parameters, parameter);
        }
    }

}
